雖然 TodoListApp
目前沒有很多 state
,但可能以後會增加很多 reducer
來處理別的狀態
在 redux 中 store 存放的狀態也叫做 state 不要跟
react class component
搞混囉!
所以我的把跟 redux 有關的都拉出來放!
在 src
底下創一個檔案夾叫做 provider
底下再創一個 todo
裝用的檔案夾
裡面我通常會放以下這些 js 檔案
src
|- provider
|- todo
|- reducer.js
|- constants.js
|- actions.js
|- selector.js
現在要使用 combineReducer 來將 todo
reducer 合在一起
combineReducer 可以幫助解決當 state 越來越複雜的時候,可以將複雜的 reducer 拆成比小的模組,然後再使用 combineReducer 將他們合起來
src/reducer.js
改成這樣
import { combineReducers } from "redux";
import {
KEY,
} from './provider/todo/constants';
import reducer from './provider/todo/reducer';
export default combineReducers({
[KEY]: reducer,
})
寫法很像 day26 寫 component state
的感覺,僅需要寫改變 state
的 handler
像是之前的 code 寫得很多 setState
將 list 存放在 component state
裡面
改成 redux 也很簡單,就是將它們搬到 reducer 裡面,但呼叫 reducer 就要使用 action 來做更新
先前寫的是這樣子的 code
componentDidMount() {
this.setState({
isLoading: true,
})
FirebaseService.getTodoList()
.then((list) => {
console.log(list)
this.setState({
list,
isLoading: false,
})
})
}
所以現在呼叫取得 list 的方法就要改變
前天看到的這個 react component
要改變 state
就要改成發 action
出去
所以可以想像這樣
componentDidMount() {
this.props.fetchTodoList() // fetchTodoList 是 action
}
action 是一個有 type 的物件,可以寫在 action.js
裡
但原本的 getList 裡有一個 非同步的請求
去拿列表,現在改成一個 action
所以昨天安裝的 redux-thunk
middleware
就可以幫助我處理非同步的事件
我的寫法像是這樣
// action creator 中 再回傳一個 function middleware 就會將 store 的 dispatch 帶進來給予使用,這樣子就可以一直 dispatch action 出去了
export const fetchTodoList = () => dispatch => {
// 更新頁面讀取狀態
dispatch(setLoading(true))
FirebaseService.getTodoList()
.then((list) => {
// 非同步更新 state
dispatch({
type: SET_TODO_LIST,
payload: list,
})
})
}
接下來就是在 reducer
中處理 SET_TODO_LIST
的這個 action
provider/todo/reducer.js
case SET_TODO_LIST:
// 取消讀取狀態
const updatedState = {
...state,
isLoading: false,
}
// 遇到錯誤
if (error) {
return updatedState;
}
// 將 list 存取或 merge
return {
...updatedState,
list: action.payload,
};
reducer 跟 action 大概是這樣運作的~
寫了 reducer 但還是不能動,因為要把 View 和 store 連接起來
redux-react
提供了 connect
的 HOC (higher order component) 幫助我們將 state 和 action 轉成 props 帶到 react component
裡面
connect 的 method 如下
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps
是一個 function
會將 state 轉換成 component 裡需要的 props
將其寫成需要連結到的 state 像是這樣
export const getList = (state) => state[KEY].list
export const getIsLoading = (state) => state[KEY].isLoading
const mapStateToProps = (state) => {
// 取得列表
// 取得讀取狀態
return {
list: getList(state),
isLoading: getIsLoading(state),
}
};
mapDispatchToProps
是一個 function
會將 dispatch
出去的 function
轉換成 component 裡需要的 props ,這樣的動作的稱之為 action creator
將它寫成這樣
const mapDispatchToProps = (dispatch) => ({
handleFetchTodoList: () => dispatch(fetchTodoList()),
handleUpdate: (id, item) => dispatch(updateItem(id, item)),
handleAdd: (name) => dispatch(addItem(name)),
handleDelete: (id) => dispatch(deleteItem(id)),
});
connect 的使用
const withConnect = connect(mapStateToProps, mapDispatchToProps);
呼叫完會拿到一個 function 然後再使用這個 function 將 component 放入
withConnect(Screen)
這邊如果有很多像 connect
這樣子的 HoC
的話可以用 compose
來組合
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(Container)
接下來將 state 都改成從 props
拿
render() {
const {
// redux State
list,
isLoading,
// redux action
handleAdd,
handleUpdate,
handleDelete,
// state 改成 props
} = this.props;
return (
<div className="container">
<ActionController
handleAdd={handleAdd}
/>
<LoadingSpinner isLoading={isLoading}/>
<TodoList
list={list}
handleUpdate={handleUpdate}
handleDelete={handleDelete}
/>
</div>
)
}
就大功告成了!
其他的 CRUD Action 和 reducer 寫法就跟 fetchList 雷同就不把 code 貼上來了
有興趣可以看完整代碼
昨天有安裝 redux-devtool
所以開發 redux 的時候可以時不時打開來看 store 裡面存放什麼資料!
這樣改完發現很多 code
是從原本 Component 裡面的 function 搬到 action
和 reducer
中,沒有像相中的難,但要做的事是將邏輯拆散到 action, constants, selector 和 reducer 中,像 TodoListApp 這個 state 僅有使用過一次的話,好像使用 redux 反而寫了一堆 code 來做事!不過這樣寫真的讓每個模組關注的地方都很專一,Component 的邏輯處理變少了,可讀性也增加了許多!